#  This file is part of Pynguin.
#
#  SPDX-FileCopyrightText: 2019-2025 Pynguin Contributors
#
#  SPDX-License-Identifier: MIT
#
import importlib
import inspect
from pathlib import Path

import pytest

import pynguin.configuration as config
import pynguin.ga.generationalgorithmfactory as gaf
import pynguin.testcase.defaulttestcase as dtc
import pynguin.testcase.statement as stmt
from pynguin.analyses.module import ModuleTestCluster, generate_test_cluster
from pynguin.analyses.typesystem import InferredSignature, NoneType
from pynguin.instrumentation.machinery import install_import_hook
from pynguin.instrumentation.tracer import SubjectProperties
from pynguin.testcase.execution import SubprocessTestCaseExecutor
from pynguin.utils.generic.genericaccessibleobject import GenericFunction
from tests.fixtures.crash.seg_fault import cause_segmentation_fault


@pytest.fixture
def cause_seg_fault_mock(type_system) -> GenericFunction:
    return GenericFunction(
        function=cause_segmentation_fault,
        inferred_signature=InferredSignature(
            signature=inspect.Signature(),
            original_return_type=NoneType(),
            original_parameters={},
            type_system=type_system,
        ),
    )


@pytest.fixture
def cause_seg_fault_test_case(cause_seg_fault_mock):
    test_case = dtc.DefaultTestCase(ModuleTestCluster(0))
    test_case.add_statement(stmt.FunctionStatement(test_case, cause_seg_fault_mock))
    return test_case


@pytest.fixture
def crash_test_expected(tmp_path) -> Path:
    expected_content = """# Test cases automatically generated by Pynguin (https://www.pynguin.eu).
# Please check them before you use them.
import tests.fixtures.crash.seg_fault as module_0


def test_case_0():
    module_0.cause_segmentation_fault()
"""
    crash_file = tmp_path / "crash_test_expected.py"
    crash_file.write_text(expected_content)
    return crash_file


@pytest.mark.skip(reason="Makes main thread on GitHub Actions crash")
def test_generate_crashing_test_integration(tmp_path: Path, crash_test_expected: Path):
    module_name = "tests.fixtures.crash.seg_fault"
    config.configuration.test_case_output.crash_path = tmp_path / "crashing_tests_seg_fault"
    config.configuration.test_case_output.crash_path.mkdir(parents=True, exist_ok=True)

    config.configuration.stopping.maximum_iterations = 10
    config.configuration.module_name = module_name
    config.configuration.search_algorithm.min_initial_tests = 2
    config.configuration.search_algorithm.max_initial_tests = 2
    config.configuration.search_algorithm.population = 2
    config.configuration.test_creation.none_weight = 1
    config.configuration.test_creation.any_weight = 1
    subject_properties = SubjectProperties()
    with install_import_hook(module_name, subject_properties):
        # Need to force reload in order to apply instrumentation.
        with subject_properties.instrumentation_tracer:
            module = importlib.import_module(module_name)
            importlib.reload(module)

        executor = SubprocessTestCaseExecutor(subject_properties)
        cluster = generate_test_cluster(module_name)
        search_algorithm = gaf.TestSuiteGenerationAlgorithmFactory(
            executor, cluster
        ).get_search_algorithm()
        test_cases = search_algorithm.generate_tests()
        assert test_cases.size() == 0

    # Check that the crashing_tests dir contains one file and compare its content
    crash_path = config.configuration.test_case_output.crash_path
    assert crash_path.exists()
    assert len(list(crash_path.iterdir())) == 1
    crash_file = next(crash_path.iterdir())
    assert crash_file.is_file()
    assert crash_file.read_text() == crash_test_expected.read_text()


@pytest.mark.skip(reason="Flaky")
def test_generate_partly_crashing_test_integration(tmp_path: Path):
    module_name = "tests.fixtures.crash.partly_crashing"
    config.configuration.test_case_output.crash_path = tmp_path / "crashing_tests_partly_crashing"
    config.configuration.test_case_output.crash_path.mkdir(parents=True, exist_ok=True)

    config.configuration.stopping.maximum_iterations = 10
    config.configuration.module_name = module_name
    config.configuration.search_algorithm.min_initial_tests = 2
    config.configuration.search_algorithm.max_initial_tests = 2
    config.configuration.search_algorithm.population = 2
    config.configuration.test_creation.none_weight = 1
    config.configuration.test_creation.any_weight = 1
    subject_properties = SubjectProperties()
    with install_import_hook(module_name, subject_properties):
        # Need to force reload in order to apply instrumentation.
        with subject_properties.instrumentation_tracer:
            module = importlib.import_module(module_name)
            importlib.reload(module)

        executor = SubprocessTestCaseExecutor(subject_properties)
        cluster = generate_test_cluster(module_name)
        search_algorithm = gaf.TestSuiteGenerationAlgorithmFactory(
            executor, cluster
        ).get_search_algorithm()
        test_cases = search_algorithm.generate_tests()
        assert test_cases.size() >= 1

    # Check that the crashing_tests dir contains one file and compare its content
    crash_path = config.configuration.test_case_output.crash_path
    assert crash_path.exists()
    assert len(list(crash_path.iterdir())) >= 1
    crash_file = next(crash_path.iterdir())
    assert crash_file.is_file()
